﻿using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Zhy.ModbusLib.Base;

namespace Zhy.ModbusLib
{
    /// <summary>
    /// Modbus 串口 RTU 与 ASCII通讯协议的父类
    /// </summary>
    public abstract class ModbusSerial: ModbusMaster
    {
        #region 属性
        /// <summary>
        /// 端口名称
        /// </summary>
        public string PortName { get; set; } = "COM2";
        /// <summary>
        /// 波特率
        /// </summary>
        public int BaudRate { get; set; } = 9600;
        /// <summary>
        /// 校验位
        /// </summary>
        public Parity Parity { get; set; } = Parity.None;
        /// <summary>
        /// 数据位
        /// </summary>
        public int DataBits { get; set; } = 8;
        /// <summary>
        /// 停止位
        /// </summary>
        public StopBits StopBits { get; set; } = StopBits.One;
        /// <summary>
        /// 超时时间 - 超时时间根据实际情况设置
        /// </summary>
        public int ReadTimeout { get; set; } = 20000; // 20秒钟 
        /// <summary>
        /// 缓冲区大小
        /// </summary>
        public int ReadBufferSize { get; set; } = 4096;
        /// <summary>
        /// 串口对象
        /// </summary>
        private SerialPort _serialPort;
        /// <summary>
        /// 用于系统线程的取消令牌
        /// </summary>
        private CancellationTokenSource cts = new CancellationTokenSource();
        #endregion 

        #region 初始化串口连接对象
        /// <summary>
        /// 初始化串口连接对象
        /// </summary>
        protected ModbusSerial()
        {
            // 实例化链接对象
            _serialPort = new SerialPort();
        }
        #endregion

        #region 串口连接
        /// <summary>
        /// 串口连接
        /// </summary>
        public override void Connect()
        {
            _serialPort.PortName = PortName; // 端口名称
            _serialPort.BaudRate = BaudRate; // 波特率
            _serialPort.DataBits = DataBits; // 数据位
            _serialPort.Parity = Parity; // 校验位
            _serialPort.StopBits = StopBits; // 停止位
            _serialPort.ReadTimeout = ReadTimeout; // 超时时间
            _serialPort.ReadBufferSize = ReadBufferSize; // 缓冲区大小
            _serialPort.Open(); // 打开连接

            // 这里可以启动一个后台线程进行队列数据的获取和请求
            Task.Run(() =>
            {
                while (!cts.IsCancellationRequested) // 如果没有被销毁请求就继续执行
                {
                    // 判断串口已连接-判断队列有任务
                    if (_serialPort == null || asyncModels.Count == 0) continue;

                    var model = asyncModels.First(); // 获取队列任务

                    Exception _ex = null; // 异常信息
                    byte[] datas = null; // 处理后的返回报文
                    try
                    {
                        // 输出要发送的报文
                        Console.WriteLine(string.Join("-", model.ReqBytes.Select(b => b.ToString("X2"))));
                        // 发送和接收报文数据
                        byte[] resp = this.SendAndReceive(model.ReqBytes, model.RespLen); 

                        // 执行校验逻辑，返回的应该是数据部分字节，同时中间可能还有异常
                        // 调用RTU的Check方法
                        // 调用ASCII的Check方法
                        datas = this.Check(resp, model.RespLen);
                    }
                    catch (Exception ex)
                    {
                        // 在发送、接收、检测报文时可能会发生异常，需要将异常信息返回。
                        _ex = ex;
                    }
                    finally
                    {
                        // 执行完成时，将结果通过委托方法传出
                        model.Completed?.Invoke(datas, model.Handler, _ex);
                        // 根据请求ID在队列中移除已执行完成的任务
                        asyncModels.RemoveAll(id => id.Handler == model.Handler);
                    }
                }
            });
        }
        #endregion

        #region 串口关闭连接
        /// <summary>
        /// 串口关闭连接
        /// </summary>
        public override void Disconnect()
        { 
            _serialPort.Close(); // 关闭连接
            cts.Cancel(); // 取消请求，- 队列线程停止运行
        }
        #endregion

        #region Modbus - 读取的发送报文拼接
        /// <summary>
        /// Modbus - 读取的发送报文拼接
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="funcCode">功能码</param>
        /// <param name="start">开始地址</param>
        /// <param name="count">获取个数</param>
        /// <returns></returns>
        public byte[] GetReadBytes(byte slave, byte funcCode, ushort start, ushort count)
        {
            return new byte[] {
               slave, // 从站地址
               funcCode, // 功能码
               (byte)(start / 256), // 起始地址-高位
               (byte)(start % 256), // 起始地址-地位
               (byte)(count / 256), // 读取个数 - 高位
               (byte)(count % 256) // 读取个数 - 低位
            };
        }
        #endregion

        #region Modbus - 写入的发送报文拼接
        /// <summary>
        /// Modbus - 写入的发送报文拼接
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="funcCode">功能码</param>
        /// <param name="start">开始地址</param>
        /// <param name="count">写入数量</param>
        /// <param name="datas">写入报文</param>
        /// <returns></returns>
        public byte[] GetWriteBytes(byte slave, byte funcCode, ushort start, int count ,byte[] datas)
        {
            // 线圈/寄存器    多写功能码进行处理
            List<byte> bytes = new List<byte>()
            {
               slave, // 从站地址
                funcCode, // 功能码
                (byte)(start / 256), // 写入地址高位
                (byte)(start % 256), // 写入地址低位
                /// 寄存器数量   datas.lenght/2
                /// 线圈数量
                (byte)(count/256), // 写入数量高位 0-65280
                (byte)(count%256), // 写入数量低位 0-255
                (byte)datas.Length, // 字节数
            };
            bytes.AddRange(datas); // 添加要写入的数据

            return bytes.ToArray(); // 返回字节数组
        }
        #endregion

        #region 发送和接收报文数据方法
        /// <summary>
        /// 发送和接收报文数据方法
        /// </summary>
        /// <param name="bytes">请求报文</param>
        /// <param name="len">正常返回报文字节数</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        protected byte[] SendAndReceive(byte[] bytes, int len)
        {
            if (!_serialPort.IsOpen)
                throw new Exception("串口对象未连接");

            _serialPort.Write(bytes.ToArray(), 0, bytes.Length); // 发送报文数据

            // 响应数据获取
            List<byte> resp = new List<byte>();
            try
            {
                // 循环读取返回的报文数据
                do
                {
                    // 这里需要配合一个超时 2000
                    resp.Add((byte)_serialPort.ReadByte());/// 从缓冲区一个字节读
                }
                while (resp.Count < len); // 判断是否小于正常返回报文字节数
            }
            catch
            {
                // 超过了设置的超时时间
                // 这里有一个超时异常
            }
            return resp.ToArray(); // 返回接收到的报文数据
        }
        #endregion

        #region 对返回的报文进行校验
        /// <summary>
        /// 对返回的报文进行校验
        /// </summary>
        /// <param name="resp">返回的报文</param>
        /// <param name="len">正常报文接收长度</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        protected virtual byte[] Check(byte[] resp, int len) { return null; }
        #endregion

        #region 校验位
        /// <summary>
        /// RTU验证码
        /// </summary>
        /// <param name="data">报文</param>
        /// <returns></returns>
        /// <exception cref="ArgumentException"></exception>
        public byte[] CRC16(List<byte> data)
        {
            if (data == null || !data.Any())
                throw new ArgumentException("");

            //运算
            ushort crc = 0xFFFF;
            for (int i = 0; i < data.Count; i++)
            {
                crc = (ushort)(crc ^ (data[i]));
                for (int j = 0; j < 8; j++)
                {
                    crc = (crc & 1) != 0 ? (ushort)((crc >> 1) ^ 0xA001) : (ushort)(crc >> 1);
                }
            }
            byte hi = (byte)((crc & 0xFF00) >> 8);  //高位置
            byte lo = (byte)(crc & 0x00FF);         //低位置

            return new byte[] { hi, lo };
        }

        /// <summary>
        /// ASCII验证码
        /// </summary>
        /// <param name="value">报文</param>
        /// <returns></returns>
        public byte LRC(byte[] value)
        {
            if (value == null) return 0x00;

            int sum = 0;
            for (int i = 0; i < value.Length; i++)
            {
                sum += value[i];
            }

            sum = sum % 256;  // 只拿低位的一个字节数据
            sum = 256 - sum;

            return (byte)sum;
        }
        #endregion
    }
}
